﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using SKYPE4COMLib;
using GSdk.Net.Lglcd;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
using GSdk.Shared;

namespace GSdk.SkypePlugin
{
    using CustomTimer = System.Timers.Timer;

    class SkypeApplet : IDisposable
    {
        public readonly object SyncRoot = new object();

        private const int SkypeProtocol = 9;
        private const int MaxMessages = 10;
        private const int MaxCharPerMessage = 38;

        private Skype SkypeApplication;
        private Applet Applet;
        private CustomTimer AttachTimer;
        private System.Timers.ElapsedEventHandler m_AttachTimerElapsedHandler;
        private EventHandler m_ShutdownAppletHandler;
        private EventHandler m_AppletConfigureHandler;

        private Bitmap m_BwImage;
        private Bitmap m_QvgaImage;

        private Graphics m_BwGraphics;
        private Graphics m_QvgaGraphics;

        private Font m_QvgaFont;
        private Font m_BwFont;
        private Font m_QvgaSmallFont;
        private Font m_BwSmallFont;

        private LinkedList<SkypeMessage> m_QvgaMessages = new LinkedList<SkypeMessage>();
        private LinkedList<SkypeMessage> m_BwMessages = new LinkedList<SkypeMessage>();
        // Double size to be sure we shouldn't reallocate it
        private StringBuilder m_Chat = new StringBuilder(MaxCharPerMessage * MaxMessages * 2);
        private TimeSpan m_QvgaTimeToRead = TimeSpan.Zero;
        private TimeSpan m_BwTimeToRead = TimeSpan.Zero;
        private SizeF m_QvgaDisplayChatSize = new SizeF(310, 200);
        private SizeF m_BwDisplayChatSize = new SizeF(160, 43);
        private System.Timers.ElapsedEventHandler m_DisplayChatTimerElapsedHandler;
        private CustomTimer DisplayChatTimer;
        
        private void MessageStatusHandler(ChatMessage pMessage, TChatMessageStatus Status)
        {
            bool updateInstant = false;
            TimeSpan lastTimeAddedQVGA = TimeSpan.Zero;
            TimeSpan lastTimeAddedBW = TimeSpan.Zero;
            bool executeGC = false;
            // Accept only some type of messages
#if !DEBUG
            if (SkypeApplication.CurrentUserHandle == pMessage.FromHandle)
                return;
            if (pMessage.Status != TChatMessageStatus.cmsReceived)
                return;
#else
            if (pMessage.Status != TChatMessageStatus.cmsReceived && pMessage.Status != TChatMessageStatus.cmsSent)
                return;
#endif
            lock (SyncRoot)
            {
                // Add new message to list
                // QVGA
                if (m_QvgaMessages.Count > 0 && m_QvgaMessages.Last.Value.Account == pMessage.FromHandle)
                {
                    m_QvgaMessages.Last.Value.Message.AddLast(pMessage.Body);
                }
                else
                {
                    m_QvgaMessages.AddLast(new SkypeMessage(pMessage.FromHandle, pMessage.FromDisplayName, pMessage.Body));
                    // Let's add some time for account too
                    lastTimeAddedQVGA += TimeSpan.FromSeconds(Properties.Settings.Default.SecondsPerWord);
                }
                //BW
                if (m_BwMessages.Count > 0 && m_BwMessages.Last.Value.Account == pMessage.FromHandle)
                {
                    m_BwMessages.Last.Value.Message.AddLast(pMessage.Body);
                }
                else
                {
                    m_BwMessages.AddLast(new SkypeMessage(pMessage.FromHandle, pMessage.FromDisplayName, pMessage.Body));
                    // Let's add some time for account too
                    lastTimeAddedBW += TimeSpan.FromSeconds(Properties.Settings.Default.SecondsPerWord);
                }

                // Add time to read the message
                lastTimeAddedQVGA += TimeSpan.FromSeconds(Properties.Settings.Default.SecondsPerWord * pMessage.Body.CountWords());
                lastTimeAddedBW += TimeSpan.FromSeconds(Properties.Settings.Default.SecondsPerWord * pMessage.Body.CountWords());
                m_QvgaTimeToRead += lastTimeAddedQVGA;
                m_BwTimeToRead += lastTimeAddedBW;

                // Run timer for graphic update and gc call
                // Check if is the first message we are reading, if that's the case, we show skypeplugin instantly
                if (m_QvgaTimeToRead == lastTimeAddedQVGA)
                    updateInstant = true;
                if (m_BwTimeToRead == lastTimeAddedBW)
                    updateInstant = true;

                if (updateInstant)
                {
                    if (Properties.Settings.Default.ShowWhenMessageIncoming)
                    {
                        Applet.GetDevices<DefaultDeviceCollection>().Qvga.ForegroundApplet = true;
                        Applet.GetDevices<DefaultDeviceCollection>().BlackAndWhite.ForegroundApplet = true;
                    }
                    UpdateGraphics();
                    executeGC = true;
                }
                DisplayChatTimer.Start();
            }
            if (executeGC)
                GC.Collect();
        }

        private void DisplayChatTimerElapsedHandler(object sender, System.Timers.ElapsedEventArgs e)
        {
            TimeSpan timespanPerElapsed;
            lock (SyncRoot)
            {
                timespanPerElapsed = TimeSpan.FromMilliseconds(DisplayChatTimer.Interval);
                if (m_QvgaTimeToRead > timespanPerElapsed)
                    m_QvgaTimeToRead -= timespanPerElapsed;
                else
                    m_QvgaTimeToRead = TimeSpan.Zero;

                if (m_BwTimeToRead > timespanPerElapsed)
                    m_BwTimeToRead -= timespanPerElapsed;
                else
                    m_BwTimeToRead = TimeSpan.Zero;

                if (m_QvgaTimeToRead == TimeSpan.Zero)
                {
                    if (Properties.Settings.Default.ShowWhenMessageIncoming)
                        Applet.GetDevices<DefaultDeviceCollection>().Qvga.ForegroundApplet = false;
                    DisplayChatTimer.Stop();
                }
                else
                {
                    if (Properties.Settings.Default.ShowWhenMessageIncoming)
                        Applet.GetDevices<DefaultDeviceCollection>().Qvga.ForegroundApplet = true;
                }

                if (m_BwTimeToRead == TimeSpan.Zero)
                {
                    if (Properties.Settings.Default.ShowWhenMessageIncoming)
                        Applet.GetDevices<DefaultDeviceCollection>().BlackAndWhite.ForegroundApplet = false;
                    DisplayChatTimer.Stop();
                }
                else
                {
                    if (Properties.Settings.Default.ShowWhenMessageIncoming)
                        Applet.GetDevices<DefaultDeviceCollection>().BlackAndWhite.ForegroundApplet = true;
                }
                UpdateGraphics();
            }
            GC.Collect();
        }

        private void ShutdownAppletHandler(object sender, EventArgs e)
        {
            App.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                App.Current.Shutdown();
            }));
        }

        private void AttachTimerElapsedHandler(object sender, System.Timers.ElapsedEventArgs e)
        {
            lock (SyncRoot)
            {
                if (!Properties.Settings.Default.AttachedToSkype)
                    AttachToSkype();
                else
                    AttachTimer.Stop();
            }
            GC.Collect();
        }

        public SkypeApplet()
        {
            m_AppletConfigureHandler = new EventHandler(AppletConfigureHandler);
            m_ShutdownAppletHandler = new EventHandler(ShutdownAppletHandler);
            m_AttachTimerElapsedHandler = new System.Timers.ElapsedEventHandler(AttachTimerElapsedHandler);
            m_DisplayChatTimerElapsedHandler = new System.Timers.ElapsedEventHandler(DisplayChatTimerElapsedHandler);

            m_QvgaSmallFont = new Font(
                "monospace",
                10.0f,
                FontStyle.Bold
            );
            m_QvgaFont = new Font(
                "monospace",
                12.0f,
                FontStyle.Bold
            );
            m_BwFont = new Font(
                "monospace",
                6.0f,
                FontStyle.Bold
            );
            m_BwSmallFont = new Font(
                "monospace",
                8.0f,
                FontStyle.Regular
            );

            // Generating basic graphics
            m_BwImage = new Bitmap(160, 43, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            m_QvgaImage = new Bitmap(320, 240, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            m_BwGraphics = Graphics.FromImage(m_BwImage);
            m_QvgaGraphics = Graphics.FromImage(m_QvgaImage);

            Applet = new Net.Lglcd.Applet()
            {
                Name = "GSdk.SkypePlugin",
                Autostartable = true,
                SupportedDevices = DeviceTypes.Qvga | DeviceTypes.BlackAndWhite,
            };
            Applet.ConnectionLost += m_ShutdownAppletHandler;
            Applet.Configure += m_AppletConfigureHandler;

            SkypeApplication = new Skype();
            SkypeApplication.MessageStatus += MessageStatusHandler;
            ((_ISkypeEvents_Event)SkypeApplication).AttachmentStatus += AttachmentStatusHandler;
            AttachTimer = new CustomTimer()
            {
                Interval = 31000,
                AutoReset = true,
            };
            AttachTimer.Elapsed += m_AttachTimerElapsedHandler;
            DisplayChatTimer = new CustomTimer()
            {
                Interval = 3000,
                AutoReset = true,
            };
            DisplayChatTimer.Elapsed += m_DisplayChatTimerElapsedHandler;

            // Start everything
            AttachTimer.Start();
            lock (SyncRoot)
                AttachToSkype();
        }

        private void AttachmentStatusHandler(TAttachmentStatus Status)
        {
            if (Status == TAttachmentStatus.apiAttachNotAvailable)
            {
                if (!AttachTimer.Enabled)
                    AttachTimer.Start();
                Properties.Settings.Default.AttachedToSkype = false;
                Properties.Settings.Default.Save();
            }
        }

        private void AppletConfigureHandler(object sender, EventArgs e)
        {
            App.Current.Dispatcher.BeginInvoke(new Action<SkypeApplet>(a =>
            {
                lock (a.SyncRoot)
                {
                    var w = new ConfigureWindow(Properties.Settings.Default.SecondsPerWord, Properties.Settings.Default.ShowWhenMessageIncoming, Properties.Settings.Default.UseMessageSeparator, Properties.Settings.Default.MessageSeparator);
                    var result = w.ShowDialog();
                    if (result.HasValue && result.Value == true)
                    {
                        Properties.Settings.Default.SecondsPerWord = w.SecondsPerWord;
                        Properties.Settings.Default.ShowWhenMessageIncoming = w.ShowWhenMessageIncoming;
                        // The replace is used to fix a bug that I think is connected with the Save method. I can't solve it so I used this horrible workaround
                        Properties.Settings.Default.MessageSeparator = w.MessageSeparator.Replace("\r\n                ", " ");
                        Properties.Settings.Default.UseMessageSeparator = w.UseMessageSeparator;
                        Properties.Settings.Default.Save();
                    }
                }
                //    App.Restart();
            }), this);
        }

        private void AttachToSkype()
        {
            try
            {
                try
                {
                    Applet.Connect();
                }
                catch
                {
                    App.Current.Shutdown();
                }

                // Run first update to show app on the device
                UpdateGraphics();

                SkypeApplication.Attach(SkypeProtocol, true);
                Properties.Settings.Default.AttachedToSkype = true;
                Properties.Settings.Default.Save();
            }
            catch (COMException e)
            {
                //Connection refused.
                //Wait timeout.
                if (e.Message.Contains("Connection refused."))
                    App.Current.Shutdown();
                if (!e.Message.Contains("Wait timeout."))
                    throw e;
            }
        }

        private void UpdateGraphics()
        {
            UpdateQvgaBitmap();
            UpdateBwBitmap();

            try
            {
                (Applet.Devices as DefaultDeviceCollection).Qvga.Update(m_QvgaImage);
            }
            catch { }
            try
            {
                (Applet.Devices as DefaultDeviceCollection).BlackAndWhite.Update(m_BwImage);
            }
            catch { }
        }

        private void UpdateQvgaBitmap()
        {
            Graphics g;

            g = m_QvgaGraphics;
            // Build header
            GraphicHelpers.CreateBasicQvgaInterface(g, m_QvgaFont, GSdk.SkypePlugin.Properties.Resources.LCD_Skypebackground, GSdk.SkypePlugin.Properties.Resources.LCDMedia_skypeIcon, Applet.Name);

            // Draw message chat
            string chatMessages = GetChatMessages(g, m_QvgaMessages, m_Chat, m_QvgaSmallFont, m_QvgaDisplayChatSize);
            g.DrawString(chatMessages, m_QvgaSmallFont, Brushes.White, new RectangleF(5, 35, 310, 200));

            g.Flush();
        }

        private void UpdateBwBitmap()
        {
            Graphics g;

            g = m_BwGraphics;
            // Build header
            // Header not used: need space in this small display!!!
            //GraphicHelpers.CreateBasicBwInterface(g, m_BwFont, Applet.Name);
            g.FillRectangle(Brushes.Black, 0, 0, 160, 43);

            // Draw message chat
            string chatMessages = GetChatMessages(g, m_BwMessages, m_Chat, m_BwSmallFont, m_BwDisplayChatSize);
            g.DrawString(chatMessages, m_BwSmallFont, Brushes.White, new RectangleF(0, 0, 160, 43));

            g.Flush();
        }

        private string GetChatMessages(Graphics g, LinkedList<SkypeMessage> messages, StringBuilder chatView, Font font, SizeF sizeToFit)
        {
            string chat = string.Empty;
            bool separatorBeforeMessage, newLineBeforeName;
            SizeF foundSize;

            bool useMessageSeparator;
            string messageSeparator;
            useMessageSeparator = Properties.Settings.Default.UseMessageSeparator;
            messageSeparator = (string)Properties.Settings.Default.MessageSeparator.Clone();

            if (messages.Count == 0)
                return chat;

            // Generate the chat string
            // This loop will not be so long as it looks because strings will be deleted each time a new message is called
            while (true)
            {
                chatView.Clear();
                if (useMessageSeparator)
                {
                    newLineBeforeName = false;
                    foreach (var msgContainer in messages)
                    {
                        if (newLineBeforeName)
                            chatView.AppendLine();
                        newLineBeforeName = true;
                        chatView.Append("[");
                        chatView.Append(msgContainer.Name);
                        chatView.Append("] ");
                        separatorBeforeMessage = false;
                        foreach (var msg in msgContainer.Message)
                        {
                            if (separatorBeforeMessage)
                                chatView.Append(messageSeparator);
                            chatView.Append(msg);
                            separatorBeforeMessage = true;
                        }
                    }
                }
                else
                {
                    newLineBeforeName = false;
                    foreach (var msgContainer in messages)
                    {
                        foreach (var msg in msgContainer.Message)
                        {
                            if (newLineBeforeName)
                                chatView.AppendLine();
                            newLineBeforeName = true;
                            chatView.Append("[");
                            chatView.Append(msgContainer.Name);
                            chatView.Append("] ");
                            chatView.Append(msg);
                        }
                    }
                }
                chat = chatView.ToString();
                foundSize = g.MeasureString(chat, font, (int)sizeToFit.Width);
                if (foundSize.Height > sizeToFit.Height)
                {
                    if (messages.Count > 1)
                    {
                        if (messages.First.Value.Message.Count > 1)
                            messages.First.Value.Message.RemoveFirst();
                        else
                            messages.RemoveFirst();
                    }
                    else
                    {
                        if (messages.Last.Value.Message.Count > 1)
                            messages.Last.Value.Message.RemoveFirst();
                        else
                            break;
                    }
                }
                else
                    break;
            }

            return chat;
        }

        #region Dispose handling
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Managed resources
            }

            // Unmanaged resources
            try
            {
                if (AttachTimer.Enabled)
                    AttachTimer.Stop();
                if (DisplayChatTimer.Enabled)
                    DisplayChatTimer.Stop();
                m_QvgaGraphics.Dispose();
                m_BwGraphics.Dispose();
                m_QvgaImage.Dispose();
                m_BwImage.Dispose();
                m_QvgaFont.Dispose();
                m_BwFont.Dispose();
                m_QvgaSmallFont.Dispose();
                m_BwSmallFont.Dispose();
                AttachTimer.Dispose();
                DisplayChatTimer.Dispose();
                Applet.Dispose();
            }
            catch { }
        }

        ~SkypeApplet()
        {
            Dispose(false);
        }
        #endregion
    }
}
